#include "VideoManagerSingleton.h"


VideoManagerSingleton*VideoManagerSingleton::instance=NULL;

VideoManagerSingleton::VideoManagerSingleton() : QThread(){
    this->isMainVideoPlaying = false;
    this->isExtractedVideoPlaying = false;
    this->isMainVideoStopped = true;
    this->readingVideoProfile = NULL;
    this->mainVideoProducer = NULL;
    this->extractedVideoProducer = NULL;
    this->mainVideoConsumer = NULL;
    this->extractedVideoConsumer = NULL;
    this->waveForm = NULL;
    this->volumeFilter = NULL;
    this->volumeProperties = NULL;

    //new
    this->mainTractor = NULL;
    this->mainTractorMultitrack = NULL;
    this->baseColorProducer = NULL;
    this->firstCompositionTransition = NULL;
    this->mixTransition = NULL;
    //for(int i=0; i<N_MAX_SUBTITLE_TRACKS; i++){
        //this->subtitleTracks[i] = NULL;
        //this->subtitleParsers[i] = NULL;
        //this->subtitleTransitions[i] = NULL;
    //}
    this->subtitleParserToDelete = new QList<SubtitleParser*>();

    this->connect(&this->extractionManager, SIGNAL(newExtractionAdded(int, int)), this, SLOT(registerNewExtraction(int, int)));

    this->updateTimer = new QTimer();
    this->connect(this->updateTimer, SIGNAL(timeout()), this, SLOT(eventuallyDeleteSubtitleParser()));
    this->connect(this->updateTimer, SIGNAL(timeout()), this, SLOT(checkIfMainVideoIsStillPlaying()));
    this->updateTimer->start(200);
    //new
    this->connect(&this->extractionManager, SIGNAL(calculingExtraction()), this, SLOT(emitCalculingExtraction()));
    //this->connect(&this->extractionManager, SIGNAL(waveFormGot()), this, SLOT(emitWaveFormGot()));
}

void VideoManagerSingleton::initMlt(int mainVideoWidgetId, int extractedVideoWidgetId){
    Mlt::Factory::init(NULL);
    this->readingVideoProfile = new Mlt::Profile("square_ntsc_wide");
    this->mainVideoConsumer = new Mlt::Consumer(*this->readingVideoProfile, "sdl_preview");
    this->mainVideoConsumer->set("window_id", mainVideoWidgetId);
    this->mainVideoConsumer->set("play.audio_buffer", 4096);
    this->extractedVideoConsumer = new Mlt::Consumer(*this->readingVideoProfile, "sdl_preview");
    this->extractedVideoConsumer->set("window_id", extractedVideoWidgetId);
    this->extractedVideoConsumer->set("play.audio_buffer", 4096);
}

VideoManagerSingleton::~VideoManagerSingleton(){
    this->mainVideoConsumer->stop();
    this->extractedVideoConsumer->stop();
    delete this->mainVideoConsumer;
    delete this->extractedVideoConsumer;
    delete this->mainVideoProducer;
    delete this->extractedVideoProducer;
    delete this->readingVideoProfile;
    delete this->waveForm;

    delete this->volumeProperties;
    delete this->volumeFilter;
    //delete this->extractions;
    //new
    this->_deleteTractorAndPlaylists();
    this->updateTimer->stop();
    delete this->updateTimer;
    delete this->subtitleParserToDelete;
    //std::cout << "before Mlt::Factory::close()" << std::endl;
    Mlt::Factory::close();
    //std::cout << "after Mlt::Factory::close()" << std::endl;
}

VideoManagerSingleton* VideoManagerSingleton::getInstance(){
    if(!instance){
        instance = new VideoManagerSingleton();
    }
    return instance;
}

void VideoManagerSingleton::openVideo(QString fileName){
    this->stopMainVideo();
    this->stopExtractedVideo();
    Mlt::Producer *producer = new Mlt::Producer(*this->readingVideoProfile, fileName.toUtf8().constData());
    if(producer->is_valid()){
        this->_freeMemoryBeforeOpeningVideo();
        //this->removeAllExtractions();
        //this->extractionManager.stopAndReinitialise();
        this->mainVideoProducer = producer;
        this->videoFileName = fileName;
        this->_initTractor(this->mainVideoProducer);
        this->extractionManager.startInformationExtraction(fileName);

        this->mainVideoConsumer->connect(*this->mainTractor);
        this->mainVideoConsumer->start();
        this->mainVideoConsumer->set("refresh", 1);
        this->isMainVideoPlaying = true;
        this->isMainVideoStopped = false;
        this->extractedVideoProducer = new Mlt::Producer(*this->readingVideoProfile, fileName.toUtf8().constData());
        this->extractedVideoConsumer->connect(*this->extractedVideoProducer);


        //properties.set("max_gain", "60dB");
        //filter->connect_producer(*this->mainVideoProducer);
        //TODO delete filter and set that in a function where we delete everything necessary
        this->volumeFilter = new Mlt::Filter(*this->readingVideoProfile, "volume");
        this->volumeProperties = new Mlt::Properties(this->volumeFilter->get_properties());
        //this->volumeProperties->set("gain", "10");
        this->mainTractor->attach(*this->volumeFilter);
        this->extractedVideoProducer->attach(*this->volumeFilter);
        //TODO also attach that to extracted video
        emit newVideoOpened();
    }
}

void VideoManagerSingleton::_freeMemoryBeforeOpeningVideo(){
    //this->mainVideoProducer->detach(this->volumeFilter);
    this->_deleteTractorAndPlaylists();
    delete this->mainVideoProducer;
    delete this->extractedVideoProducer;
    delete this->volumeFilter;
    delete this->volumeProperties;
    this->mainVideoProducer = NULL;
    this->extractedVideoProducer = NULL;
    this->volumeFilter = NULL;
    this->volumeProperties = NULL; //TODO create a function that intialise to NULL everything related to an opened video
}

void VideoManagerSingleton::createRestoreFile(){
    QFile file("crash.txt");
    if(file.open(QIODevice::WriteOnly | QIODevice::Text)){
        QTextStream out(&file);
        out << this->videoFileName << "\n";
        file.close();
    }
}

void VideoManagerSingleton::addExtractionInRestoreFile(Extraction extraction){
    QFile file("crash.txt");
    if(file.open(QIODevice::Append | QIODevice::Text)){
        QTextStream out(&file);
        out << extraction.start << "\n";
        out << extraction.end << "\n";
        file.close();
    }
}

void VideoManagerSingleton::restoreAfterCrash(){
    QFile file("crash.txt");
    if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
        QTextStream in(&file);
        QString fileName = in.readLine();
        this->openVideo(fileName);
        this->_restoreExtractions(in);
        file.close();
    }
}

void VideoManagerSingleton::_restoreExtractions(QTextStream &in){
    while(!in.atEnd()){
        Extraction extraction;
        extraction.start = in.readLine().toInt();
        extraction.end = in.readLine().toInt();
        this->extractionManager.appendExtraction(extraction);
    }
}

void VideoManagerSingleton::playMainVideo(){
    if(this->mainTractor){
        if(!this->extractedVideoConsumer->is_stopped()){
            this->pauseConsumerExtractedVideo();
        }
        if(this->mainVideoConsumer->is_stopped()){
            if(this->isMainVideoStopped){
                this->_playMainVideoAfterStop();
            }else{
                this->_playMainVideoAfterExtract();
            }
        }else{
            this->_playMainVideoAfterPause();
        }
    }
}

void VideoManagerSingleton::_playMainVideoAfterStop(){
    if(!this->videoFileName.isEmpty()){
        this->openVideo(this->videoFileName);
    }
}

void VideoManagerSingleton::_playMainVideoAfterExtract(){
    this->mainVideoConsumer->start();
    this->mainTractor->set_speed(1.0);
    this->isMainVideoPlaying = true;
    this->isMainVideoStopped = false;
    this->mainVideoConsumer->set("refresh", 1);
}

void VideoManagerSingleton::_playMainVideoAfterPause(){
    this->mainTractor->set_speed(1.0);
    this->mainVideoConsumer->set("refresh", 1);
    this->isMainVideoPlaying = true;
    this->isMainVideoStopped = false;
}

bool VideoManagerSingleton::isPlayingMainVideo(){
    return this->isMainVideoPlaying;
}

bool VideoManagerSingleton::isPlayingExtractedVideo(){
    return this->isExtractedVideoPlaying;
}

bool VideoManagerSingleton::isStopMainVideo(){
    return this->isMainVideoStopped;
}
void VideoManagerSingleton::pauseMainVideo(){
    if(this->mainTractor){
        this->mainTractor->set_speed(0.0);
        this->isMainVideoPlaying = false;
    }
}

void VideoManagerSingleton::pauseConsumerMainVideo(){
    this->pauseMainVideo();
    this->mainVideoConsumer->stop();
}

void VideoManagerSingleton::stopMainVideo(){
    this->mainVideoConsumer->stop();
    this->isMainVideoStopped = true;
    this->isMainVideoPlaying = false;
}

void VideoManagerSingleton::pauseExtractedVideo(){
    if(this->extractedVideoProducer){
        this->isExtractedVideoPlaying = false;
        this->extractedVideoProducer->set_speed(0);
    }
}


void VideoManagerSingleton::playExtractedVideo(){
    if(this->extractedVideoProducer){
        if(!this->isMainVideoStopped){
            this->pauseConsumerMainVideo();
        }
        if(this->extractedVideoConsumer->is_stopped()){
            this->extractedVideoConsumer->start();
        }
        this->extractedVideoProducer->set_speed(1.0);
        this->extractedVideoConsumer->set("refresh", 1);
        this->isExtractedVideoPlaying = true;
    }
}

void VideoManagerSingleton::stopExtractedVideo(){
    this->extractedVideoConsumer->stop();
    this->isExtractedVideoPlaying = false;
}

void VideoManagerSingleton::seekExtractedVideo(int idFrame){
    if(this->extractedVideoProducer){
        this->extractedVideoProducer->seek(idFrame);
    }
}

void VideoManagerSingleton::pauseConsumerExtractedVideo(){
    this->pauseExtractedVideo();
    this->extractedVideoConsumer->stop();
}


int VideoManagerSingleton::getNFrames(){
    return this->mainVideoProducer->get_length(); //I don't think we need to use the tractor here
}

double VideoManagerSingleton::getFps(){
    return this->mainVideoProducer->get_fps();
}

int VideoManagerSingleton::getCurrentTimeMainVideo(){
    return this->getCurrentPositionMainVideo()/this->getFps();
}

int VideoManagerSingleton::getCurrentPositionMainVideo(){
    return this->mainTractor->position();
}

int VideoManagerSingleton::getMaxTimeMainVideo(){
    return this->getNFrames() / this->getFps();
}

int VideoManagerSingleton::getCurrentPositionExtractedVideo(){
    return this->extractedVideoProducer->position();
}

int VideoManagerSingleton::getCurrentTimeExtractedVideo(){
    return this->getCurrentPositionExtractedVideo()/this->getFps();
}

void VideoManagerSingleton::seekMainVideo(int idFrame){
    this->mainTractor->seek(idFrame);
}

void VideoManagerSingleton::_extractWaveForm(){
    //TODO find out why this doesn't work
    //*
    Mlt::Profile profile("square_ntsc_wide");
    Mlt::Producer producer(profile, this->videoFileName.toUtf8().constData());
    int width = 200;
    int height = 200; //the higher those 2 data the higher the precision of the waveform
    Mlt::Frame *frame;
    int nFrames = this->getNFrames();
    delete this->waveForm;
    this->waveForm = new float[nFrames];
    for(int i=0; i<nFrames; i++){
            frame = producer.get_frame(i);
            unsigned char* waves = frame->get_waveform(width, height);
            float mean = 0;
            for(int j=0; j<width*height; j++){
                    mean += int(waves[j]);
            }
            mean /= width*height;
            this->waveForm[i] = mean;
    }
    //*/
}

void VideoManagerSingleton::addExtractionWithSimpleMethod(int nSecBefore, int nSecAfter){
    //TODO create a better algorithm
    int currentPosition = this->getCurrentPositionMainVideo();
    this->extractionManager.addNewExtractionFromSimpleMethod(currentPosition, nSecBefore, nSecAfter);
}

void VideoManagerSingleton::addExtractionWithAutomaticMethod(int nMaxSecBefore, int nMaxSecAfter, double sensibility, int medianDataSize){
    int currentPosition = this->getCurrentPositionMainVideo();
    this->extractionManager.addNewExtractionFromAutomaticMethod(currentPosition, nMaxSecBefore, nMaxSecAfter, sensibility, medianDataSize);
}

void VideoManagerSingleton::registerNewExtraction(int start, int end){
    Extraction extraction;
    extraction.start = start;
    extraction.end = end;
    this->addExtractionInRestoreFile(extraction);
    emit newExtractionAdded(start, end);
}

Extraction VideoManagerSingleton::getExtraction(int id){
    return this->extractionManager.getExtraction(id);
}

Extraction VideoManagerSingleton::getLastExtraction(){
    return this->extractionManager.getLastExtraction();
}

int VideoManagerSingleton::getNExtractions(){
    return this->extractionManager.getNExtractions();
}

void VideoManagerSingleton::removeExtraction(int id){
    this->extractionManager.removeExtraction(id);
}

void VideoManagerSingleton::removeAllExtractions(){
    for(int i=this->getNExtractions()-1; i>=0; i--){
        this->removeExtraction(i);
    }
}

void VideoManagerSingleton::selectExtraction(int id){
    //TODO see if we pause the extracted video
    Extraction extraction = this->getExtraction(id);
    this->extractedVideoProducer->seek(extraction.start);
}

void VideoManagerSingleton::replaceExtraction(int indice, Extraction newExtraction){
    this->extractionManager.replaceExtraction(indice, newExtraction);
}

void VideoManagerSingleton::setVolume(int percentage){
    if(this->isPlayingMainVideo()){
        this->volumeProperties->set("gain", percentage/100.0);
        //this->volumeProperties->set("end", percentage/100.0);
    }
}

void VideoManagerSingleton::run(){
    //TODO je m'arrete la
    Mlt::Playlist playlist;
    Mlt::Producer newProducer(*this->readingVideoProfile, this->videoFileName.toUtf8().data());
    playlist.append(newProducer);
    this->_removeRegionFromPlaylist(playlist);

    QString id("avformat");
    if(this->audioOnly){
        id = QString("sdl_audio");
    }
    Mlt::Consumer outFileConsumer(*this->readingVideoProfile, id.toStdString().data(), this->outVideoFileName.toUtf8().constData());
    outFileConsumer.connect(playlist);
    outFileConsumer.run();
    outFileConsumer.stop();
    outFileConsumer.purge();
    emit videoSaved();
}

void VideoManagerSingleton::saveExtractions(QString fileName, bool audioOnly){
    this->outVideoFileName = fileName;
    this->audioOnly = audioOnly;
    if(!this->isRunning()){
        this->start();
    }

}

void VideoManagerSingleton::_removeRegionFromPlaylist(Mlt::Playlist &playlist){
    //int nExtractions = this->getNExtractions();
    int lastLeftFrame = getNFrames();
    Extraction extraction;
    for(int i=this->getNExtractions()-1; i>=0; i--){
        extraction = this->getExtraction(i);
        if(extraction.end < lastLeftFrame){
            playlist.remove_region(extraction.end, lastLeftFrame-extraction.end);
        }
        lastLeftFrame = extraction.start;
    }
    extraction = this->getExtraction(0);
    if(extraction.start > 0){
            playlist.remove_region(0, extraction.start);
    }
}

// new from here
void VideoManagerSingleton::_initTractor(Mlt::Producer *mainVideoProducer){
    this->mainTractor = new Mlt::Tractor();
    this->mainTractorMultitrack = this->mainTractor->multitrack();
    this->baseColorProducer = new Mlt::Producer(*this->readingVideoProfile, "color");
    Mlt::Properties propColor(this->baseColorProducer->get_properties());
    propColor.set("in", 0);
    int lenghtMainProducer = mainVideoProducer->get_length();
    propColor.set("out", lenghtMainProducer);
    propColor.set("novdpau", 1);
    this->mainTractorMultitrack->connect(*this->baseColorProducer, 0);

    Mlt::Properties propVideo(mainVideoProducer->get_properties());
    propVideo.set("novdpau", 1);
    this->mainTractorMultitrack->connect(*mainVideoProducer, 1);
    this->firstCompositionTransition = Mlt::Factory::transition(*this->readingVideoProfile, (char*)"composite", NULL); //TODO find out how to get rid of the warning
    Mlt::Properties propTrans01(this->firstCompositionTransition->get_properties());
    propTrans01.set("out", lenghtMainProducer);
    propTrans01.set("progressive", 1);
    propTrans01.set("start", "0,0:100%x100%");
    propTrans01.set("geometry", "0%,0%:100%x100%:100");
    propTrans01.set("distort", 1);
    propTrans01.set("fill", 1);
    this->mainTractor->plant_transition(this->firstCompositionTransition, 0, 1);
    this->mixTransition = Mlt::Factory::transition(*this->readingVideoProfile, (char*)"mix", NULL);
    Mlt::Properties propTrans02(this->mixTransition->get_properties());
    propTrans02.set("out", 400);
    propTrans02.set("combine", 1);
    propTrans02.set("always_active", 1);
    this->mainTractor->plant_transition( this->mixTransition, 0, 1);

}

void VideoManagerSingleton::_deleteTractorAndPlaylists(){
    //for(int i=0; i<N_MAX_SUBTITLE_TRACKS; i++){
        //delete this->subtitleTransitions[i];
        //this->_deletePlaylist(this->subtitleTracks[i]);
        //this->_deleteSubtitleParser(this->subtitleParsers[i]);
    //}
    if(this->mainTractor != NULL){
        this->mainTractor->clear();
    }
    delete this->mainTractor;
    delete this->baseColorProducer;
    delete this->firstCompositionTransition;
    delete this->mixTransition;
}

void VideoManagerSingleton::_deleteSubtitleParser(SubtitleParser *subtitleParser){
    if(subtitleParser != NULL){
        if(subtitleParser->isRunning()){
            this->subtitleParserToDelete->append(subtitleParser);
        }else{
            delete subtitleParser;
        }
    }
}

void VideoManagerSingleton::_deletePlaylist(Mlt::Playlist *playlist){
    if(playlist != NULL){
        int nSub = playlist->count();
        Mlt::Producer *producer;
        for(int i=nSub-1; i>=0; i--){
            producer = playlist->get_clip(i);
            playlist->remove(i);
            delete producer;
        }
        delete playlist;
    }
}

void VideoManagerSingleton::setSubtitleTrack(int idSubtitleTrack, QString strFileName, QFont font, QColor color, double yPerc){
    SubtitleParser* subParser = new SubtitleParser();
    //SubtitleParser subParser;
    subParser->addSubtitleInTractor(this->mainTractor, this->getNFrames(), strFileName, idSubtitleTrack, QSize(854, 480), font, color, yPerc, this->getFps());
    this->subtitleParserToDelete->append(subParser); //with that we got a segmentation fault
}


void VideoManagerSingleton::eventuallyDeleteSubtitleParser(){
    if(this->subtitleParserToDelete != NULL){
        for(int i=this->subtitleParserToDelete->count()-1; i>=0; i--){
            SubtitleParser *subParser = this->subtitleParserToDelete->at(i);
            if(!subParser->isRunning()){
                this->subtitleParserToDelete->removeAt(i);
                delete subParser;
            }
        }
    }
}

void VideoManagerSingleton::checkIfMainVideoIsStillPlaying(){
    if(this->isMainVideoPlaying && this->mainTractor->position() == this->getNFrames()){
        //this->isMainVideoPlaying = false;
        emit videoFinished();
    }
}

bool VideoManagerSingleton::isCalculingWaveform(){
    return this->extractionManager.isCalculatingWaveform();
}

bool VideoManagerSingleton::isCalculingExtraction(){
    return this->extractionManager.isCalculatingExtraction();
}

void VideoManagerSingleton::emitCalculingExtraction(){
    emit calculingExtraction();
}

//void VideoManagerSingleton::emitWaveFormGot(){
    //emit waveFormGot();
//}


